package com.xhj.mybatis.scripting.xmltags;

import com.xhj.mybatis.parsing.GenericTokenParser;
import com.xhj.mybatis.parsing.TokenHandler;
import com.xhj.mybatis.type.SimpleTypeRegistry;

import java.util.regex.Pattern;

/**
 * TextSqlNode
 *
 * @author XJks
 * @description TextSqlNode 类用于处理 MyBatis XML 中的文本 SQL 节点，支持动态 SQL 解析和参数绑定。
 */
public class TextSqlNode implements SqlNode {
    /**
     * 保存 SQL 文本内容
     */
    private final String text;

    /**
     * 用于 SQL 注入检查的正则表达式过滤器
     */
    private final Pattern injectionFilter;

    /**
     * 构造方法，只传入 SQL 文本，不做注入检查
     *
     * @param text SQL 文本
     */
    public TextSqlNode(String text) {
        this(text, null);
    }

    /**
     * 构造方法，传入 SQL 文本和注入检查的正则表达式
     *
     * @param text            SQL 文本
     * @param injectionFilter 注入检查的正则表达式
     */
    public TextSqlNode(String text, Pattern injectionFilter) {
        this.text = text;
        this.injectionFilter = injectionFilter;
    }

    /**
     * 判断 SQL 是否为动态 SQL（即是否包含 ${} 占位符）。
     * TODO:
     *  1.注意这里只检查 ${}，不检查 #{}。
     *  2.${} 占位符代表“动态 SQL”——即 SQL 语句在运行时会根据参数进行拼接和替换。
     *  3.动态 SQL 的含义是：SQL 语句的某些部分不是静态写死的，而是会根据实际传入的参数动态生成。
     *  4.具体来说，${} 里的内容会被解析为参数值并直接拼接到 SQL 字符串中，这样 SQL 语句的结构和内容就可以根据不同的输入发生变化。相比之下，#{} 是预编译参数，不会影响 SQL 结构，只是传值。
     *  5.所以，这里判断是否包含 ${}，就是判断 SQL 是否会因为参数不同而动态变化结构或内容，这就是“动态 SQL”的核心含义。
     *
     * @return 如果是动态 SQL 返回 true，否则返回 false
     */
    public boolean isDynamic() {
        // 创建一个动态检查器
        DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
        // 创建一个通用的 Token 解析器，解析 ${} 占位符
        GenericTokenParser parser = createParser(checker);
        // 解析 SQL 文本
        parser.parse(text);
        // 返回是否为动态 SQL
        return checker.isDynamic();
    }

    /**
     * apply 方法将当前节点应用到传入的 DynamicContext（动态上下文）中。
     *
     * @param context 包含正在构建的 SQL 文本、参数映射、变量等运行时信息。
     * @return 返回 true：表示该节点已成功应用，处理流程可以继续执行后续节点。返回 false：表示该节点决定停止后续节点的执行（例如 if/choose 条件不满足时）。
     */
    @Override
    public boolean apply(DynamicContext context) {
        // 创建一个绑定参数的 Token 解析器
        GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        // 解析 SQL 并追加到上下文
        context.appendSql(parser.parse(text));
        return true;
    }

    /**
     * 创建一个通用的 Token 解析器，解析 ${} 占位符
     *
     * @param handler Token 处理器
     * @return 通用 Token 解析器
     */
    private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
    }

    /**
     * 内部类：用于处理 ${} 占位符并绑定参数
     */
    private static class BindingTokenParser implements TokenHandler {

        /**
         * 动态 SQL 上下文
         */
        private DynamicContext context;

        /**
         * 注入检查的正则表达式
         */
        private Pattern injectionFilter;

        /**
         * 构造方法
         *
         * @param context         动态 SQL 上下文
         * @param injectionFilter 注入检查的正则表达式
         */
        public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
            this.context = context;
            this.injectionFilter = injectionFilter;
        }

        /**
         * 处理 ${} 占位符内容，进行参数绑定和注入检查
         *
         * @param content 占位符内容
         * @return 绑定后的参数值字符串
         */
        @Override
        public String handleToken(String content) {
            // 获取参数
            Object parameter = context.getBindings().get("_parameter");
            // 根据参数类型进行不同处理
            if (parameter == null) {
                // 参数为 null，绑定 value 为 null
                context.getBindings().put("value", null);
            } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
                // 如果参数是简单类型，直接绑定 value
                context.getBindings().put("value", parameter);
            }
            // 使用 OGNL 表达式获取实际值
            Object value = OgnlCache.getValue(content, context.getBindings());
            // 如果值为 null，返回空字符串，否则转为字符串
            String srtValue = value == null ? "" : String.valueOf(value);
            // 检查 SQL 注入
            checkInjection(srtValue);
            return srtValue;
        }

        /**
         * 检查参数值是否符合注入过滤规则
         *
         * @param value 参数值
         */
        private void checkInjection(String value) {
            if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
                // 不符合正则，抛出异常
                throw new RuntimeException("Invalid input. Please conform to regex" + injectionFilter.pattern());
            }
        }
    }

    /**
     * 内部类：用于检查 SQL 是否为动态 SQL
     */
    private static class DynamicCheckerTokenParser implements TokenHandler {

        /**
         * 标记是否为动态 SQL
         */
        private boolean isDynamic;

        /**
         * 构造方法
         */
        public DynamicCheckerTokenParser() {
            // 防止合成访问
        }

        /**
         * 返回是否为动态 SQL
         *
         * @return true 表示动态 SQL
         */
        public boolean isDynamic() {
            return isDynamic;
        }

        /**
         * 处理 ${} 占位符内容，发现即标记为动态 SQL
         *
         * @param content 占位符内容
         * @return 返回 null
         */
        @Override
        public String handleToken(String content) {
            this.isDynamic = true;
            return null;
        }
    }

}
